package validator

import (
	"fmt"

	"github.com/distribution/reference"
	"github.com/pkg/errors"
	"github.com/stackrox/rox/central/vulnmgmt/vulnerabilityrequest/common"
	"github.com/stackrox/rox/generated/storage"
	"github.com/stackrox/rox/pkg/errorhelpers"
	"github.com/stackrox/rox/pkg/errox"
)

// ValidateNewSuppressVulnRequest ensures that the new request to suppress vulnerability is correct, if not, returns an error.
func ValidateNewSuppressVulnRequest(req *storage.VulnerabilityRequest) error {
	var errList errorhelpers.ErrorList
	errList.AddError(validateNoID(req))
	errList.AddError(validateUser(req))
	errList.AddError(validateComment(req))
	errList.AddError(validateNoApprovedStatusForNewRequests(req))
	errList.AddError(validateNoDeniedStatus(req))
	errList.AddError(validateTargetState(req))
	errList.AddError(validateScope(req))
	errList.AddError(validateCVEs(req))
	errList.AddError(validatNotExpired(req))
	errList.AddErrors(validateNewRequestNotUpdated(req))
	if err := errList.ToError(); err != nil {
		return errox.InvalidArgs.CausedBy(errList.ToError())
	}
	return nil
}

// ValidateUpdate validates that the update request is correct, if not, returns an error.
func ValidateUpdate(update *common.UpdateRequest) error {
	if update == nil || (update.DeferralUpdate == nil && update.FalsePositiveUpdate == nil) {
		return errox.InvalidArgs.CausedBy("nothing to update - update info must be specified")
	}
	if update.Comment == "" {
		return errox.InvalidArgs.CausedBy("comment is required")
	}
	if update.DeferralUpdate != nil && len(update.DeferralUpdate.GetCVEs()) == 0 {
		return errox.InvalidArgs.CausedBy("all CVEs cannot be removed from the request")
	}
	if update.FalsePositiveUpdate != nil && len(update.FalsePositiveUpdate.GetCVEs()) == 0 {
		return errox.InvalidArgs.CausedBy("all CVEs cannot be removed from the request")
	}
	return nil
}

func validateNoID(req *storage.VulnerabilityRequest) error {
	if req.GetId() != "" {
		return errors.New("new vulnerability exception must not have ID")
	}
	return nil
}

func validateUser(req *storage.VulnerabilityRequest) error {
	if req.GetRequestor() == nil {
		return errors.New("vulnerability exception must have a requestor")
	}
	return nil
}

func validateComment(req *storage.VulnerabilityRequest) error {
	if len(req.GetComments()) == 0 {
		return errors.New("vulnerability exception must have at least one comment")
	}
	return nil
}

func validateTargetState(req *storage.VulnerabilityRequest) error {
	if req.GetTargetState() != storage.VulnerabilityState_DEFERRED &&
		req.GetTargetState() != storage.VulnerabilityState_FALSE_POSITIVE {
		return errors.New("request to suppress vulnerability must be a deferral or false-positive request")
	}

	if req.GetTargetState() == storage.VulnerabilityState_DEFERRED {
		if req.GetDeferralReq() == nil || req.GetDeferralReq().GetExpiry() == nil {
			return errors.New("vulnerability deferral request invalid. Deferral expiry not provided")
		}
	}

	if req.GetTargetState() == storage.VulnerabilityState_FALSE_POSITIVE {
		if req.GetFpRequest() == nil {
			return errors.New("request to mark vulnerability as false-positive is invalid. False-positive configuration not provided")
		}
	}
	return nil
}

func validateNoApprovedStatusForNewRequests(req *storage.VulnerabilityRequest) error {
	if req.GetStatus() == storage.RequestStatus_APPROVED || req.GetStatus() == storage.RequestStatus_APPROVED_PENDING_UPDATE {
		return errors.New("new vulnerability exception must not be in approved state")
	}
	return nil
}

func validateNoDeniedStatus(req *storage.VulnerabilityRequest) error {
	if req.GetStatus() == storage.RequestStatus_DENIED {
		return errors.New("new vulnerability exception must not be in denied state")
	}
	return nil
}

func validateScope(req *storage.VulnerabilityRequest) error {
	if req.GetScope() == nil || (req.GetScope().GetImageScope() == nil && req.GetScope().GetGlobalScope() == nil) {
		return errors.New("vulnerability exception must have scope")
	}

	if imageScope := req.GetScope().GetImageScope(); imageScope != nil {
		if imageScope.GetRegistry() == "" {
			return errors.New("vulnerability exception with image scope must have image registry")
		}
		if imageScope.GetRemote() == "" {
			return errors.New("vulnerability exception with image scope must have image name")
		}
		var imageName string
		if imageScope.GetRegistry() != ".*" && imageScope.GetRemote() != ".*" {
			imageName = fmt.Sprintf("%s/%s", imageScope.GetRegistry(), imageScope.GetRemote())
		}
		if imageScope.GetTag() != ".*" && imageScope.GetTag() != "" {
			imageName = fmt.Sprintf("%s/%s:%s", imageScope.GetRegistry(), imageScope.GetRemote(), imageScope.GetTag())
		}
		if imageName != "" {
			if _, err := reference.ParseAnyReference(imageName); err != nil {
				return errors.Errorf("vulnerability exception has invalid image scope %s", imageName)
			}
		}
	}

	return nil
}

func validateCVEs(req *storage.VulnerabilityRequest) error {
	if len(req.GetCves().GetCves()) == 0 {
		return errors.New("request must indicate the vulnerabilities for which request is opened")
	}
	return nil
}

func validatNotExpired(req *storage.VulnerabilityRequest) error {
	if req.GetExpired() {
		return errors.New("expected vulnerability exception to be not expired")
	}
	return nil
}

func validateNewRequestNotUpdated(req *storage.VulnerabilityRequest) error {
	if req.GetUpdatedReq() != nil {
		return errors.New("expected new vulnerability exception, not an updated one")
	}
	return nil
}
